# -*- coding:utf-8 -*-

'''
PROJECT_NAME : python_高级
file    : test1
author  : 1032162439@qq.com
date    : 2022-11-14 13:55
IDE     : PyCharm
'''

import socket
import selectors
from io import BytesIO

try:
    from selectors import (
        SelectSelector,
        PollSelector,
        EpollSelector,
        KqueueSelector
    )
except ImportError:
    pass

from selectors import (
    DefaultSelector,
    EVENT_READ,
    EVENT_WRITE
)


class RequestHandler:
    selector = DefaultSelector()

    tasks = {"unfinished": 0}

    def __init__(self):
        self.tasks['unfinished'] += 1
        self.client = socket.socket()
        self.client.setblocking(False)
        self.buffer = BytesIO()

    def get_result(self):
        try:
            self.client.connect(('192.168.154.108', 9999))
        except BlockingIOError:
            pass
        self.selector.register(self.client.fileno(),EVENT_WRITE,self.send)

    def send(self, key):
        payload = (f"GET / HTTP/1.1\r\n"
                   f"Host: 192.168.154.108:9999\r\n"
                   "Connection: close\r\n\r\n")
        # 执行此函数，说明事件已经触发
        # 我们要将绑定的回调函数取消
        self.selector.unregister(key.fd)
        # 发送请求
        self.client.send(payload.encode("utf-8"))
        # 请求发送之后就要接收了，但是啥时候能接收呢？
        # 还是要交给操作系统，所以仍然需要注册回调
        self.selector.register(self.client.fileno(),
                               EVENT_READ,
                               self.recv)
        # 表示当 self.client 这个 socket 满足可读时
        # 就去执行 self.recv
        # 翻译过来就是数据返回了，就去接收数据

    def recv(self, key):
        """
        数据返回时执行的回调函数
        :param key:
        :return:
        """
        # 接收数据，但是只收了 1024 个字节
        # 如果实际返回的数据超过了 1024 个字节怎么办？
        data = self.client.recv(1024)
        # 很简单，只要数据没收完，那么数据到来时就会可读
        # 那么会再次调用此函数，直到数据接收完为止
        # 注意：此时是非阻塞的，数据有多少就收多少
        # 没有接收的数据，会等到下一次再接收
        # 所以这里不能写 while True
        if data:
            # 如果有数据，那么写入到 buffer 中
            self.buffer.write(data)
        else:
            # 否则说明数据读完了，那么将注册的回调取消
            self.selector.unregister(key.fd)
            # 此时就拿到了所有的数据
            all_data = self.buffer.getvalue()
            # 按照 \r\n\r\n 进行分隔得到列表
            # 第一个元素是响应头，第二个元素是响应体
            result = all_data.split(b"\r\n\r\n")[1]
            print(f"result: {result.decode('utf-8')}")
            self.client.close()
            self.tasks["unfinished"] -= 1

    @classmethod
    def run_until_complete(cls):
        # 基于 IO 多路复用创建事件循环
        # 驱动内核不断轮询 socket，检测事件是否发生
        # 当事件发生时，调用相应的回调函数
        while cls.tasks["unfinished"]:
            # 轮询，返回事件已经就绪的 socket
            ready = cls.selector.select()
            # 这个 key 就是回调里面的 key
            for key, mask in ready:
                # 拿到回调函数并调用，这一步需要我们手动完成
                callback = key.data
                callback(key)
        # 因此当事件发生时，调用绑定的回调，就是这么实现的
        # 整个过程就是给 socket 绑定一个事件 + 回调
        # 事件循环不停地轮询检测，一旦事件发生就会告知我们
        # 但是调用回调不是内核自动完成的，而是由我们手动完成的

        # "非阻塞 + 回调 + 基于 IO 多路复用的事件循环"
        # 所有框架基本都是这个套路




def main():
    import time
    start = time.perf_counter()
    for _ in range(10):
        # 这里面只是注册了回调，但还没有真正执行
        RequestHandler().get_result()
    # 创建事件循环，驱动执行
    RequestHandler.run_until_complete()
    end = time.perf_counter()
    print(f"总耗时: {end - start}")


if __name__ == '__main__':
    main()
